// SPDX-License-Identifier: GPL-2.0
/*
 *  Copyright(C) 2005-2006, Thomas Gleixner <tglx@linutronix.de>
 *  Copyright(C) 2005-2007, Red Hat, Inc., Ingo Molnar
 *  Copyright(C) 2006-2007  Timesys Corp., Thomas Gleixner
 *
 *  NOHZ implementation for low and high resolution timers
 *
 *  Started by: Thomas Gleixner and Ingo Molnar
 */
#include <linux/compiler.h>
#include <linux/tick.h>
#include <linux/percpu.h>

#include "../inc/tick.h"
#include "../inc/tick-sched.h"

#ifdef CONFIG_NO_HZ_COMMON
static bool can_stop_idle_tick(int cpu, struct tick_sched *ts)
{
    if (unlikely(!tick_sched_flag_test(ts, TS_FLAG_NOHZ)))
        return false;

    return true;
}

/**
 * tick_nohz_next_event() - return the clock monotonic based next event
 * @ts:		pointer to tick_sched struct
 * @cpu:	CPU number
 *
 * Return:
 * *%0		- When the next event is a maximum of TICK_NSEC in the future
 *		  and the tick is not stopped yet
 * *%next_event	- Next event based on clock monotonic
 */
static ktime_t tick_nohz_next_event(struct tick_sched *ts, int cpu)
{
    u64 basemono, next_tick, delta, expires;
    unsigned long basejiff;
    int tick_cpu;

    basemono = get_jiffies_update(&basejiff);

    next_tick = get_next_timer_interrupt(basejiff, basemono);

    expires = 5 * (u64)1000000000 + ktime_get();//todo

    ts->timer_expires = min_t(u64, expires, next_tick);

    return ts->timer_expires;
}

static void tick_nohz_stop_tick(struct tick_sched *ts, int cpu)
{
    u64 expires = ts->timer_expires;

    tick_sched_flag_set(ts, TS_FLAG_STOPPED);
    hrtimer_cancel(&ts->sched_timer);

    hrtimer_start(&ts->sched_timer, expires,
			      HRTIMER_MODE_ABS_PINNED_HARD);
}

static inline void tick_nohz_retain_tick(struct tick_sched *ts)
{
    ts->timer_expires_base = 0;
}

static void tick_nohz_restart(struct tick_sched *ts, ktime_t now)
{
	hrtimer_set_expires(&ts->sched_timer, now);

	/* Forward the time to expire in the future */
	hrtimer_forward(&ts->sched_timer, now, TICK_NSEC);

    hrtimer_start_expires(&ts->sched_timer, HRTIMER_MODE_ABS_PINNED_HARD);
	/*
	 * Reset to make sure the next tick stop doesn't get fooled by past
	 * cached clock deadline.
	 */
	ts->next_tick = 0;
}

static void tick_nohz_restart_sched_tick(struct tick_sched *ts, ktime_t now)
{
	/* Update jiffies first */
	tick_do_update_jiffies64(ts, now);

	/* Cancel the scheduled timer and restore the tick: */
	tick_sched_flag_clear(ts, TS_FLAG_STOPPED);
	tick_nohz_restart(ts, now);
}

static void tick_nohz_account_idle_time(struct tick_sched *ts,
					ktime_t now)
{
}

static void tick_nohz_idle_update_tick(struct tick_sched *ts, ktime_t now)
{
    tick_nohz_restart_sched_tick(ts, now);
}

void tick_nohz_activate(struct tick_sched *ts)
{
    tick_sched_flag_set(ts, TS_FLAG_NOHZ);
}

/**
 * tick_nohz_idle_enter - prepare for entering idle on the current CPU
 *
 * Called when we start the idle loop.
 */
void tick_nohz_idle_enter(void)
{
}

void tick_nohz_idle_exit(void)
{
    struct tick_sched *ts = this_cpu_tick_sched();
    ktime_t now;
    bool tick_stopped;

    tick_stopped = tick_sched_flag_test(ts, TS_FLAG_STOPPED);

    if (tick_stopped)
        now = ktime_get();

	if (tick_stopped)
		tick_nohz_idle_update_tick(ts, now);
}

/**
 * tick_nohz_idle_stop_tick - stop the idle tick from the idle task
 *
 * When the next event is more than a tick into the future, stop the idle tick
 */
void tick_nohz_idle_stop_tick(void)
{
    struct tick_sched *ts = this_cpu_tick_sched();   
    ktime_t expires;
    int cpu;
    int was_stopped = tick_sched_flag_test(ts, TS_FLAG_STOPPED);

    if (was_stopped)
       return;

    cpu = smp_processor_id();

    /*
     * If tick_nohz_get_sleep_length() ran tick_nohz_next_event(), the
     * tick timer expiration time is known already.
     */
    if (ts->timer_expires_base)
        expires = ts->timer_expires;
    else if (can_stop_idle_tick(cpu, ts))
        expires = tick_nohz_next_event(ts, cpu);
    else
        return;

    if (expires > 0)
    {
        tick_nohz_stop_tick(ts, cpu);

        if (!was_stopped && tick_sched_flag_test(ts, TS_FLAG_STOPPED))
        {
            ts->idle_jiffies = ts->last_jiffies;
        }
    }
    else
    {
        tick_nohz_retain_tick(ts);
    }
}

void tick_nohz_idle_restart_tick(void)
{
    struct tick_sched *ts = this_cpu_tick_sched();

    if (tick_sched_flag_test(ts, TS_FLAG_STOPPED))
    {
        ktime_t now = ktime_get();

        tick_nohz_restart_sched_tick(ts, now);
        tick_nohz_account_idle_time(ts, now);
    }
}

void tick_nohz_init(void)
{
}

#endif
